﻿using System;
using System.Net.NetworkInformation;

namespace Aeon.Emulator.Net
{
    /// <summary>
    /// Represents a 48-bit IPX node address.
    /// </summary>
    public struct NodeAddress : IEquatable<NodeAddress>
    {
        private byte byte1;
        private byte byte2;
        private byte byte3;
        private byte byte4;
        private byte byte5;

        /// <summary>
        /// An empty node address of all zeros.
        /// </summary>
        public static readonly NodeAddress Empty = new NodeAddress();

        /// <summary>
        /// Initializes a new instance of the <see cref="NodeAddress"/> struct.
        /// </summary>
        /// <param name="address">The address as an array of at least 5 bytes.</param>
        public NodeAddress(byte[] address)
        {
            if(address == null)
                throw new ArgumentNullException("address");
            if(address.Length < 5)
                throw new ArgumentException("Address must be at least 5 bytes.");

            this.byte1 = address[0];
            this.byte2 = address[1];
            this.byte3 = address[2];
            this.byte4 = address[3];
            this.byte5 = address[4];
        }

        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="nodeA">The node A.</param>
        /// <param name="nodeB">The node B.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static bool operator ==(NodeAddress nodeA, NodeAddress nodeB)
        {
            return nodeA.Equals(nodeB);
        }
        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="nodeA">The node A.</param>
        /// <param name="nodeB">The node B.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static bool operator !=(NodeAddress nodeA, NodeAddress nodeB)
        {
            return !nodeA.Equals(nodeB);
        }

        /// <summary>
        /// Gets the <see cref="System.Byte"/> at the specified index.
        /// </summary>
        public byte this[int index]
        {
            get
            {
                switch(index)
                {
                case 0: return this.byte1;
                case 1: return this.byte2;
                case 2: return this.byte3;
                case 3: return this.byte4;
                case 4: return this.byte5;
                default: throw new IndexOutOfRangeException();
                }
            }
        }

        /// <summary>
        /// Returns a NodeAddress based on the system's MAC address.
        /// </summary>
        /// <returns>NodeAddress generated from the MAC address.</returns>
        public static NodeAddress GetFromMacAddress()
        {
            var ip = IPGlobalProperties.GetIPGlobalProperties();
            var interfaces = NetworkInterface.GetAllNetworkInterfaces();
            foreach(var iface in interfaces)
                return new NodeAddress(iface.GetPhysicalAddress().GetAddressBytes());

            return Empty;
        }

        /// <summary>
        /// Returns an array of the bytes in the NodeAddress.
        /// </summary>
        /// <returns>Array of the bytes in the NodeAddress.</returns>
        public byte[] ToArray()
        {
            return new[] { byte1, byte2, byte3, byte4, byte5 };
        }
        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public override int GetHashCode()
        {
            return (this.byte1 << 24) | (this.byte2 << 16) | (this.byte3 << 8) | this.byte4;
        }
        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        public bool Equals(NodeAddress other)
        {
            return this.GetHashCode() == other.GetHashCode() && this.byte5 == other.byte5;
        }
        /// <summary>
        /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
        /// <returns>
        ///   <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        public override bool Equals(object obj)
        {
            if(obj is NodeAddress)
                return Equals((NodeAddress)obj);
            else
                return false;
        }
        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return string.Format("{0:X2}:{1:X2}:{2:X2}:{3:X2}:{4:X2}", this.byte1, this.byte2, this.byte3, this.byte4, this.byte5);
        }
    }
}
